#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>

#include "common.hpp"
#include "log.hpp"

extern Log server_log;

namespace qyc
{
    // 正排元素
    typedef struct doc_info_index
    {
        doc_info_t _html;
        uint64_t _doc_id;   // 文档ID
    }doc_info_index_t;

    // 倒排元素
    typedef struct invert_elem
    {
        uint64_t _doc_id;   // 文档ID
        size_t _weight;     // 文档权重
        std::string _word;  // 关键字
    }invert_elem_t;

    class doc_index
    {
    public:
        typedef std::vector<invert_elem_t> invert_list;
    private:
        doc_index()
        {}
        doc_index(const doc_index&) = delete;
        doc_index& operator=(const doc_index&) = delete;
        static doc_index* _instance_index;
    public:
        static doc_index* get_instance()
        {
            static std::mutex mtx;
            if (_instance_index == nullptr)
            {
                std::lock_guard<std::mutex> lg(mtx);
                if (_instance_index == nullptr)
                    _instance_index = new doc_index;
            }
            return _instance_index;
        }
    public:
        // 根据正排ID找到对应文档
        doc_info_index_t* get_forward_index(const uint64_t doc_id)
        {
            if (doc_id >= _forward_index.size())
            {
                // std::cerr << "doc_id out of range err" << std::endl;
                server_log(Fatal, "doc_id out of range err, err file: %s, err line: %s", __FILE__, __LINE__);
                return nullptr;
            }
            return &(_forward_index[doc_id]);
        }
        // 根据关键字key,获得对应倒排表
        invert_list* get_invert_list(const std::string& key)
        {
            auto it = _invert_index.find(key);
            if (it == _invert_index.end())
            {
                // std::cerr << key <<" not have invertlist" << std::endl;
                server_log(Info, "%s not have invertlist", key.c_str());
                return nullptr;
            }
            return &(it->second);
        }
        // 根据去标签解析清理过后的文档建立正排和倒排索引
        bool build_index(const std::string& path)
        {
            std::ifstream fin;
            fin.open(path, std::ifstream::in | std::ifstream::binary);
            if (!fin.is_open())
            {
                // std::cerr << path << " open err" << std::endl;
                server_log(Fatal, "%s open err, err file: %s, err line: %s", path.c_str(), __FILE__, __LINE__);
                return false;
            }      
#ifdef __TEST__DEBUG__1
            int cnt = 0;
#endif
            std::string line;
            while (std::getline(fin, line))
            {          
#ifdef __TEST__DEBUG__1
                ++cnt;
                if (cnt % 50 == 0)
                    std::cout << "在处理第" << cnt << "个文档" << std::endl;
#endif
                doc_info_index_t* html = build_forward_index(line);
                if (html == nullptr)
                {
                    // std::cerr << line << "build forward index err" << std::endl;
                    server_log(Fatal, "%s build forward index err, err file: %s, err line: %s", line.c_str(), __FILE__, __LINE__);
                    continue;
                }
                if (!build_invert_list(*html))
                {
                    // std::cerr << line << "build invert list err" << std::endl;
                    server_log(Fatal, "%s build invert list err, err file: %s, err line: %s", line.c_str(), __FILE__, __LINE__);
                    continue;
                }
            }
        }
    private:
        doc_info_index_t* build_forward_index(const std::string& info)
        {
            doc_info_index_t html_index;
            if (!string_html::string_to_html(info, html_index._html))
                return nullptr;
            html_index._doc_id = _forward_index.size();
            _forward_index.push_back(std::move(html_index));
            return &(_forward_index.back());
        }
        bool build_invert_list(const doc_info_index_t& info)
        {
            typedef struct word_cnt
            {
                int title_cnt = 0;
                int desc_cnt = 0;
            }word_cnt_t;

            // 存储词频的映射表
            std::unordered_map<std::string, word_cnt_t> word_map;

            std::vector<std::string> words;
            // 整理标题的词频
            string_word::cut_string(info._html._title, words);
            for (std::string word : words)
            {
                // 统一对字母转小写存入
                boost::to_lower(word);
                ++(word_map[word].title_cnt);
            }
            words.clear();
            // 整理内容的词频
            string_word::cut_string(info._html._context, words);
            for (std::string word : words)
            {
                boost::to_lower(word);
                ++(word_map[word].desc_cnt);
            }

            // 遍历该文档的词库,构建倒排索引
            for (auto it = word_map.begin(); it != word_map.end(); ++it)
            {
                invert_elem_t invert_elem;
                invert_elem._word = it->first;
                // 关键字权值计算:在标题出现次数 * 10 + 在内容出现次数
                invert_elem._weight = it->second.desc_cnt + it->second.title_cnt + 10;
                invert_elem._doc_id = info._doc_id;
                _invert_index[invert_elem._word].push_back(std::move(invert_elem));
            }

            return true;
        } 
    private:
        // 使用数组作为正排索引的存储结构,下标天然是文档ID
        std::vector<doc_info_index_t> _forward_index;  // 正排索引
        // 倒排索引一定是一个关键字和一组倒排元素对应
        std::unordered_map<std::string, invert_list> _invert_index;    // 倒排索引
    };
    doc_index* doc_index::_instance_index = nullptr;
}
